Clean Code 第三章-函数

3.1 短小

  • 一个函数最多不要超过20行,每行不应该有150个字符那么长
  • 代码块和缩进
    if,else,while其中的代码块应该只有一行,该行应该是一个函数调用语句。

3.2 只做一件事

讨论

  • 方法一:如果函数只是做了该函数名下同一抽象层上的步骤,则函数只做了一件事。
  • 方法二:看能否再拆出一个函数,该函数不仅只是单纯的重现诠释其实现

3.3 每个函数一个抽象层级

自顶向下读代码:向下规则

例如:
把大象装进冰箱里的程序。
主函数层是这样几步:

  1. 把冰箱门打开;
  2. 把大象装进去;
  3. 把冰箱门关上。

其中引用了三个函数,拿函数 1.“把冰箱门打开”来看又细分为如下步骤:

  1. 找到门把手;
  2. 判断把手的触发类型;
  3. 将门把手调整为开门状态;
  4. 判断门是朝哪个方向开;
  5. 打开门(返回)。

3.4 switch 语句

写出短小的switch,if/else语句很难,因为天生要做大于 2 件事。不过我们可以都在同一抽象层级来做事,而且不会重复。

  • 我们可以利用多态来实现:利用工厂

将创建对象的逻辑放到工厂里面,隐藏具体实现细节

3.5 使用描述性的名称

  • 别害怕长名称,长而具有描述性的名称,要比短而令人费解的名称好。
  • 使用某种命名约定,让函数名称中的多个单词容易阅读
  • 别害怕花时间取名字。可以尝试不同的名称,测试其阅读效果。使用现代的 IDE 改名字非常简单,想到好名字就改掉
  • 命名方式要保持一致。使用与模块名相同的短语、名词、动词给函数命名。如:
    includeSetup,includeSetupPages,inludeSuiteSetup

3.6 函数参数

最理想的参数数量是零,其次是一、再其次是二。尽量避免三个或以上的参数

1. 一个函数的普遍形式

2. Boolean 参数

Boolean 参数丑陋不堪,如render(isSuite:bool)应该讲函数一份为二:renderForSuite()renderForSigle

3. 二个参数

  • 两个参数要比一个参数难懂。 如:writeFile(name)writeFile(outStream,name)
  • 当然有的时候两个参数正好。如:new Point(0,0)
  • 二个参数不算恶劣。但是你可以尽量写成一个参数。如将writeFile(outStream,name),作为成员变量,outStream.writeFile(name)。还可以分离出新类。

4. 三个参数

有三个参数的函数要比两个参数难懂的多。比如:
assertEquals(msg,expected,acture),总不能做到第一时间理解。

5. 参数对象

从参数创建对象,从而减少参数数量看起来像是作弊。但是当一组参数被共同传递。就像x和y一样。往往就是有自己名称概念的一部分。

6.参数列表

String.format("%s %.2f",name,hours)其实和上面的参数对象一样。

7.动词与关键字

  • 函数和参数应当是一种非常好的 “动词/名词形式” 比如: write(name).
  • assertEqual改成 assertExpectedEqualsActural()可能会好些,大大减轻了记忆参数顺序的负担

7.无副作用

  • 副作用是一种谎言,函数承诺做一件事,但是还是会做其他被隐藏的事。
  • 如果有副作用,那么方法名要包含副作用
1
2
3
4
5
checkPassword(name,psw){
if(name&&psw){
init()//副作用
}
}

8.分割指令与询问

函数要么做什么事,要么回答什么事。

1
2
3
4
5
6
7
if(set("username","jerry")){//考虑下,这里是什么意思?看起来不清晰。到底是检查已经设置为jerry了?还是是否设置成功呢?

}
// 更好的方式:分离指令与询问。
if(isExist("username")){
set("username","jerry")
}

9 使用异常代替返回错误码

  • 指令式错误码轻微违反了指令与询问分隔的规则。
  • 它鼓励了在 if 语句判断中把指令当表达式来用。
    会导致更深的层次嵌套。
1
2
3
4
5
6
7
if(deletePage(page) == E_OK){
if(deleteReference(page.name) == E_OK){
//...层层判断
}
}else{
log("failed")
}

9.1 抽离 Try/Catch 代码块

1
2
3
4
5
6
try{
deletePage(page)
deleteReference(page.name)
}catch(e){
log(e)
}

9.2 错误处理就是一件事

  • 意味着如果 try 开头,就应该是函数内容的第一个单词。
  • catch/finally{}后面也不应该有内容

9.3 Error.java 依赖磁铁

建议我们使用异常代替错误码

10 别重复自己

重复是一切邪恶的根源。许多原则和实践规则都是为了消除重复。

12 如何写出这样的函数?

  • 写代码和写文章一样,想到什么就写什么。然后再打磨它。
  • 没有人能够一次性写好,然后我们一步一步打磨:分解函数、修改名称、消除重复…

小结

  • 大师级程序员把系统当做故事来讲,而不是当做程序来写。
  • 本章讲的是编写良好的函数机制。如果遵循规则,函数就会短小,有个好名字。
  • 不过永远不要忘记真正的目标在于讲述系统的故事。而你编写的函数必须干净利落的拼装到一起,形成一种精确而清晰的语言,帮助你讲故事。